﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using NGinnBPM.Lib.Data;
using System.IO;
using NLog;
using System.Xml.XPath;
using System.Xml.Linq;
using NGinnBPM.Lib.Interfaces;
using NGinnBPM.Lib.Services;
using NGinnBPM.Lib.Schema.Xsd;
using NGinnBPM.Lib.Exceptions;
using System.Xml.Linq;
using System.Runtime.Serialization;
using Newtonsoft.Json;


namespace NGinnBPM.Lib.Schema
{
    /// <summary>
    /// Builds process definitions from XML
    /// </summary>
    public class XmlProcessDefinitionBuilder
    {
        private Logger log = LogManager.GetCurrentClassLogger();
        public static readonly string PACKAGE_NAMESPACE = "http://www.nginn.org/PackageDefinition.1_0";
        public static readonly string PROCESS_NAMESPACE = "http://www.nginn.org/WorkflowDefinition.1_0.xsd";
        
        public ProcessDefinition LoadProcessDefinition(XmlReader xr, PackageDefinition def)
        {
            throw new NotImplementedException();
        }


        public TypeSet LoadTypeSet(string fileName)
        {
            using (FileStream fs = new FileStream(fileName, FileMode.Open))
            {
                return LoadTypeSet(fs);
            }
        }

        

        public TypeSet LoadTypeSet(Stream stm)
        {
            XmlReaderSettings rs = new XmlReaderSettings();
            XmlReader xr = XmlReader.Create(stm, rs);
            XPathDocument xpd = new XPathDocument(xr);
            XPathNavigator navig = xpd.CreateNavigator();
            navig.MoveToFirstChild();
            return LoadTypeSet(navig);
        }

        /// <summary>
        /// Load a type set from XPathNavigator.
        /// Root node should be an xml element enclosing the 
        /// data type definitions
        /// </summary>
        /// <param name="rootNode"></param>
        /// <returns></returns>
        public TypeSet LoadTypeSet(XPathNavigator rootNode)
        {
            if (rootNode.NodeType != XPathNodeType.Element)
                throw new ArgumentException("Not an element");
            TypeSet ts = new TypeSet();
             XPathNodeIterator it = rootNode.SelectChildren(XPathNodeType.Element);
             while (it.MoveNext())
             {
                 if (it.Current.LocalName == "struct")
                 {
                     StructDef sd = LoadStructDef(it.Current, ts);
                     ts.AddType(sd);
                 }
                 else if (it.Current.LocalName == "enum")
                 {
                     EnumDef enu = LoadEnumDef(it.Current, ts);
                     ts.AddType(enu);
                 }
                 else throw new Exception("Unexpected node: " + it.Current.LocalName);
             }
             return ts;
        }

        /// <summary>
        /// Load struct definition
        /// </summary>
        /// <param name="nav"></param>
        /// <returns></returns>
        protected StructDef LoadStructDef(XPathNavigator nav, TypeSet ts)
        {
            StructDef sd = new StructDef();
            sd.Name = nav.GetAttribute("name", "");
            LoadExtensionAttributes(nav, sd);
            if (sd.Name == null || sd.Name.Length == 0) throw new Exception("Missing name in struct definition");
            XPathNodeIterator it = nav.SelectChildren(XPathNodeType.Element);
            while (it.MoveNext())
            {
                if (it.Current.LocalName == "member")
                {
                    MemberDef md = LoadMemberDef(it.Current, ts);
                    sd.Members.Add(md);
                }
                else throw new Exception("Unexpected node in struct: " + it.Current.LocalName);
            }
            return sd;
        }

        protected MemberDef LoadMemberDef(XPathNavigator nav, TypeSet ts)
        {
            MemberDef md = new MemberDef();
            md.Name = nav.GetAttribute("name", "");
            md.TypeName = nav.GetAttribute("type", "");
            string t = nav.GetAttribute("isArray", "");
            md.IsArray = "true".Equals(t) || "1".Equals(t);
            t = nav.GetAttribute("required", "");
            md.IsRequired = t == null || t.Length == 0 || "true".Equals(t) || "1".Equals(t);
            LoadExtensionAttributes(nav, md);
            return md;
        }

        protected EnumDef LoadEnumDef(XPathNavigator nav, TypeSet ts)
        {
            EnumDef ed = new EnumDef();
            ed.ParentTypeSet = ts;
            ed.Name = nav.GetAttribute("name", "");
            LoadExtensionAttributes(nav, ed);
            XPathNodeIterator it = nav.SelectChildren(XPathNodeType.Element);
            while (it.MoveNext())
            {
                if (it.Current.LocalName == "value")
                {
                    //object ev = Convert.ChangeType(it.Current.Value, ed.BaseType.ValueType);
                    ed.EnumValues.Add(it.Current.Value);
                }
            }
            return ed;
        }

        public ProcessDefinition LoadProcessDefinition(string fileName, PackageDefinition pd)
        {
            using (FileStream fs = new FileStream(fileName, FileMode.Open))
            {
                return LoadProcessDefinition(fs, pd);
            }
        }

        public ProcessDefinition LoadProcessDefinition(Stream stm, PackageDefinition pd)
        {
            XmlReaderSettings rs = new XmlReaderSettings();
            rs.ValidationType = ValidationType.Schema;
            AssemblyResourceXmlResolver res = new AssemblyResourceXmlResolver();
            res.DefaultPrefix = "asm://dummy/NGinnBPM.Lib.Schema.Xsd/";
            rs.Schemas.XmlResolver = res;
            rs.Schemas.Add(XmlProcessDefinitionBuilder.PROCESS_NAMESPACE, "ProcessDefinition.xsd");

            XmlReader xr = XmlReader.Create(stm, rs);
            XPathDocument xpd = new XPathDocument(xr);
            XPathNavigator navig = xpd.CreateNavigator();
            navig.MoveToFirstChild();
            return LoadProcessDefinition(navig, pd);
        }

        public ProcessDefinition LoadProcessDefinition(XPathNavigator nav, PackageDefinition pkg)
        {
            if (nav.NodeType != XPathNodeType.Element)
                throw new Exception();
            string name = nav.GetAttribute("name", "");
            string v = nav.GetAttribute("version", "");
            ProcessDefinition pd = new ProcessDefinition();
            pd.Name = name;
            pd.PackageName = pkg.Name;
            pd.DisplayName = nav.GetAttribute("displayName", "");
            pd.Version = Int32.Parse(v);
            foreach (TypeSet ts in pkg.PackageTypes)
                pd.DataTypes.ImportTypes(ts);
            
            LoadExtensionAttributes(nav, pd);
            XPathNodeIterator it = nav.SelectChildren(XPathNodeType.Element);
            while (it.MoveNext())
            {
                if (it.Current.LocalName == "description")
                {
                    pd.Body.Description = it.Current.Value;
                }
                else if (it.Current.LocalName == "dataTypes")
                {
                    LoadProcessDataTypes(it.Current, pd);
                }
                else if (it.Current.LocalName == "variables")
                {
                    XPathNodeIterator it2 = it.Current.SelectChildren(XPathNodeType.Element);
                    while (it2.MoveNext())
                    {
                        if (it2.Current.LocalName == "variable")
                        {
                            VariableDef vd = LoadVariable(it2.Current);
                            pd.Body.AddVariable(vd);
                        }
                        else throw new Exception();
                    }
                }
                else if (it.Current.LocalName == "beforeEnableScript")
                {
                    pd.Body.BeforeEnableScript = it.Current.Value;
                }
                else if (it.Current.LocalName == "afterEnableScript")
                {
                    pd.Body.AfterEnableScript = it.Current.Value;
                }
                else if (it.Current.LocalName == "onCompleteScript")
                {
                    pd.Body.BeforeCompleteScript = it.Current.Value;
                }
                else if (it.Current.LocalName == "body")
                {
                    LoadProcessBody(it.Current, pd);
                }
                else if (it.Current.LocalName == "extensionProperties")
                {
                    //LoadExtensionProperties(pd, it.Current);
                }
                else throw new Exception("Unexpected child node of <process> : " + it.Current.LocalName);
            }
            pd.Body.InitSchema(null, pd);
            //log.Info("PD Serialized: {0}", pd.SerializeToXmlString());
            return pd;
        }

        protected void LoadProcessDataTypes(XPathNavigator nav, ProcessDefinition pd)
        {
            TypeSet ts = LoadTypeSet(nav);
            pd.DataTypes = ts;
            return;
        }

       

        protected void LoadProcessBody(XPathNavigator nav, ProcessDefinition pd)
        {
            if (nav.LocalName != "body") throw new Exception();
            LoadCompositeTaskBody(nav, pd.Body);
        }

        protected void LoadCompositeTaskBody(XPathNavigator nav, CompositeTask ct)
        {
            if (nav.LocalName != "body") throw new Exception();
            XPathNodeIterator it = nav.SelectChildren(XPathNodeType.Element);
            while (it.MoveNext())
            {
                if (it.Current.LocalName == "places")
                {
                    XPathNodeIterator it2 = it.Current.SelectChildren(XPathNodeType.Element);
                    while (it2.MoveNext())
                    {
                        if (it2.Current.LocalName == "place")
                        {
                            Place pl = LoadPlace(it2.Current);
                            ct.AddPlace(pl);
                        }
                        else throw new Exception("Unexpected child of <places>: " + it.Current.LocalName);
                    }
                }
                else if (it.Current.LocalName == "tasks")
                {
                    XPathNodeIterator it2 = it.Current.SelectChildren(XPathNodeType.Element);
                    while (it2.MoveNext())
                    {
                        if (it2.Current.LocalName == "task")
                        {
                            AtomicTask at = LoadAtomicTask(it2.Current);
                            ct.AddTask(at);
                        }
                        else if (it2.Current.LocalName == "composite")
                        {
                            CompositeTask newCt = LoadCompositeTask(it2.Current);
                            ct.AddTask(newCt);
                        }
                        else throw new Exception("Unexpected child of <tasks>: " + it.Current.LocalName);
                    }
                }
                else if (it.Current.LocalName == "flows")
                {
                    XPathNodeIterator it2 = it.Current.SelectChildren(XPathNodeType.Element);
                    while (it2.MoveNext())
                    {
                        if (it2.Current.LocalName == "flow")
                        {
                            Flow fl = LoadFlow(it2.Current, ct);
                            ct.AddFlow(fl);
                        }
                        else throw new Exception("Unexpected child of <flows>: " + it.Current.LocalName);
                    }
                }
                else throw new Exception("Unexpected child of <body>: " + it.Current.LocalName);
            }
        }

        protected Place LoadPlace(XPathNavigator nav)
        {
            if (nav.LocalName != "place") throw new Exception();
            Place pl = new Place();
            pl.Id = nav.GetAttribute("id", "");
            pl.Label = nav.GetAttribute("label", "");
            string s = nav.GetAttribute("type", "");
            if (s != null && s.Length > 0)
                pl.PlaceType = (PlaceTypes)Enum.Parse(typeof(PlaceTypes), s);
            LoadExtensionAttributes(nav, pl);
            XPathNodeIterator it = nav.SelectChildren(XPathNodeType.Element);
            while (it.MoveNext())
            {
                if (it.Current.LocalName == "description")
                {
                    pl.Description = it.Current.Value;
                }
                else if (it.Current.LocalName == "extensionProperties")
                {
                    //LoadExtensionProperties(pl, it.Current);
                }
                else throw new Exception("Unexpected child node of place: " + it.Current.LocalName);
            }
            return pl;
        }

        /// <summary>
        /// TODO: custom type creation
        /// </summary>
        /// <param name="nav"></param>
        /// <returns></returns>
        protected AtomicTask LoadAtomicTask(XPathNavigator nav)
        {
            if (nav.LocalName != "task") throw new Exception();
            string id = nav.GetAttribute("id", "");
            string t = nav.GetAttribute("type", "");
            if (t == null || t.Length == 0) throw new NGinnException("Missing task type in task: " + id);
            NGinnTaskType tt = (NGinnTaskType)Enum.Parse(typeof(NGinnTaskType), t);
            string cusType = nav.GetAttribute("customType", "");
            AtomicTask at = new AtomicTask { TaskType = tt, CustomType = cusType }; // (AtomicTask)TaskDefinitionFactory.CreateTaskDefinition(tt, cusType);
            
            LoadCommonTaskData(nav, at);
            return at;
        }

        protected CompositeTask LoadCompositeTask(XPathNavigator nav)
        {
            if (nav.LocalName != "composite") throw new Exception();
            CompositeTask ct = new CompositeTask();
            LoadCommonTaskData(nav, ct);
            return ct;
        }

        protected void LoadCommonTaskData(XPathNavigator nav, Task tsk)
        {
            if (nav.LocalName != "task" && nav.LocalName != "composite") throw new Exception();
            tsk.Id = nav.GetAttribute("id", "");
            tsk.Label = nav.GetAttribute("label", "");
            string s = nav.GetAttribute("joinType", "");
            if (s != null && s.Length > 0)
                tsk.JoinType = (TaskSplitType)Enum.Parse(typeof(TaskSplitType), s);
            s = nav.GetAttribute("splitType", "");
            if (s != null && s.Length > 0)
                tsk.SplitType = (TaskSplitType)Enum.Parse(typeof(TaskSplitType), s);
            s = nav.GetAttribute("autoBindVariables", "");
            if (s != null && s.Length > 0)
                tsk.AutoBindVariables = "true".Equals(s) || "1".Equals(s);
            LoadExtensionAttributes(nav, tsk);
            XPathNodeIterator it = nav.SelectChildren(XPathNodeType.Element);
            while (it.MoveNext())
            {
                if (it.Current.LocalName == "description")
                {
                    tsk.Description = it.Current.Value;
                }
                else if (it.Current.LocalName == "variables")
                {
                    XPathNodeIterator it2 = it.Current.SelectChildren(XPathNodeType.Element);
                    while (it2.MoveNext())
                    {
                        if (it2.Current.LocalName == "variable")
                        {
                            VariableDef vd = LoadVariable(it2.Current);
                            tsk.AddVariable(vd);
                        }
                        else throw new Exception();
                    }
                }
                else if (it.Current.LocalName == "parameters")
                {
                    XPathNodeIterator it2 = it.Current.SelectChildren(XPathNodeType.Element);
                    while (it2.MoveNext())
                    {
                        if (it2.Current.LocalName == "param")
                        {
                            VariableBinding vb = LoadVariableBinding(it2.Current);
                            tsk.AddInputParameterBinding(vb);
                        }
                        else throw new Exception();
                    }
                }
                else if (it.Current.LocalName == "outParamBindings")
                {
                    XPathNodeIterator it2 = it.Current.SelectChildren(XPathNodeType.Element);
                    while (it2.MoveNext())
                    {
                        if (it2.Current.LocalName == "bind")
                        {
                            VariableBinding vb = LoadVariableBinding(it2.Current);
                            tsk.AddOutputParameterBinding(vb);
                        }
                        else throw new Exception();
                    }
                }
                else if (it.Current.LocalName == "inputBindings")
                {
                    XPathNodeIterator it2 = it.Current.SelectChildren(XPathNodeType.Element);
                    while (it2.MoveNext())
                    {
                        if (it2.Current.LocalName == "bind")
                        {
                            VariableBinding b = LoadVariableBinding(it2.Current);
                            tsk.AddInputBinding(b);
                        }
                        else throw new Exception();
                    }
                }
                else if (it.Current.LocalName == "outputBindings")
                {
                    XPathNodeIterator it2 = it.Current.SelectChildren(XPathNodeType.Element);
                    while (it2.MoveNext())
                    {
                        if (it2.Current.LocalName == "bind")
                        {
                            VariableBinding b = LoadVariableBinding(it2.Current);
                            tsk.AddOutputBinding(b);
                        }
                        else throw new Exception();
                    }
                }
                else if (it.Current.LocalName == "multiInstanceBinding")
                {
                    tsk.MultiInstanceSplitExpression = null;
                    tsk.MultiInstanceResultsBinding = null;
                    XPathNodeIterator it2 = it.Current.SelectChildren(XPathNodeType.Element);
                    while (it2.MoveNext())
                    {
                        if (it2.Current.LocalName == "splitExpression")
                        {
                            tsk.MultiInstanceSplitExpression = it2.Current.Value;
                        }
                        else if (it2.Current.LocalName == "itemAlias")
                        {
                            tsk.MultiInstanceItemAlias = it2.Current.Value;
                        }
                        else if (it2.Current.LocalName == "resultsVariable")
                        {
                            tsk.MultiInstanceResultsBinding = it2.Current.Value;
                        }
                        else throw new Exception();
                    }
                }
                else if (it.Current.LocalName == "beforeEnableScript")
                {
                    tsk.BeforeEnableScript = it.Current.Value;
                }
                else if (it.Current.LocalName == "afterEnableScript")
                {
                    tsk.AfterEnableScript = it.Current.Value;
                }
                else if (it.Current.LocalName == "onCompleteScript")
                {
                    tsk.BeforeCompleteScript = it.Current.Value;
                }
                else if (it.Current.LocalName == "extensionProperties")
                {
                    //LoadExtensionProperties(tsk, it.Current);
                }
                else if (it.Current.LocalName == "body")
                {
                    if (!(tsk is CompositeTask)) throw new Exception();
                    LoadCompositeTaskBody(it.Current, (CompositeTask)tsk);
                }
                else throw new Exception("Unexpected child node in task definition: " + it.Current.LocalName);
            }
        }

        protected void LoadExtensionAttributes(XPathNavigator nav, IHaveExtensionProperties props)
        {
            nav = nav.Clone();
            if (nav.MoveToFirstAttribute())
            {
                do
                {
                    if (nav.NamespaceURI == XmlProcessDefinitionBuilder.PROCESS_NAMESPACE)
                        continue;
                    if (nav.NamespaceURI.Length == 0)
                        continue;
                    props.SetExtensionProperty(nav.NamespaceURI, nav.LocalName, nav.Value);
                       
                } while(nav.MoveToNextAttribute());
                
            }
        }

        /*protected void LoadExtensionProperties(IHaveExtensionProperties obj, XPathNavigator nav)
        {
            XPathNodeIterator it = nav.SelectChildren(XPathNodeType.Element);
            while (it.MoveNext())
            {
                if (it.Current.LocalName == "property")
                {
                    string name =  it.Current.GetAttribute("name", "");
                    if (name != null && name.Length > 0) obj.SetExtensionProperty(name, it.Current.Value);
                }
                else throw new Exception("Unexpected child node of extensionProperties: " + it.Current.LocalName);
            }

        }*/

        protected VariableDef LoadVariable(XPathNavigator nav)
        {
            VariableDef vd = new VariableDef();
            vd.Name = nav.GetAttribute("name", "");
            LoadExtensionAttributes(nav, vd);
            vd.TypeName = nav.GetAttribute("type", "");
            vd.VariableDir = (VariableDef.Dir)Enum.Parse(typeof(VariableDef.Dir), nav.GetAttribute("dir", ""));
            string s = nav.GetAttribute("required", "");
            if ("1".Equals(s) || "true".Equals(s)) vd.IsRequired = true;
            s = nav.GetAttribute("isArray", "");
            if ("1".Equals(s) || "true".Equals(s)) vd.IsArray = true;
            XPathNodeIterator n2 = nav.SelectChildren("defaultValue", PROCESS_NAMESPACE);
            if (n2.MoveNext())
            {
                vd.DefaultValueExpr = n2.Current.Value;
            }
            return vd;
        }

        protected VariableBinding LoadVariableBinding(XPathNavigator nav)
        {
            VariableBinding vb = new VariableBinding();
            vb.VariableName = nav.GetAttribute("variable", "");
            XPathNodeIterator it = nav.SelectChildren(XPathNodeType.Element);
            while (it.MoveNext())
            {
                if (it.Current.LocalName == "value")
                {
                    vb.Expression = it.Current.Value;
                    vb.BindType = VariableBindingType.Literal;
                }
                else if (it.Current.LocalName == "expr")
                {
                    vb.Expression = it.Current.Value;
                    vb.BindType = VariableBindingType.Expr;
                }
                else if (it.Current.LocalName == "sourceVar")
                {
                    vb.Expression = it.Current.Value;
                    vb.BindType = VariableBindingType.CopyVar;
                }
                else throw new Exception();
            }
            return vb;
        }

        protected Flow LoadFlow(XPathNavigator nav, CompositeTask parentTask)
        {
            Flow fl = new Flow();
            string from = nav.GetAttribute("from", "");
            string to = nav.GetAttribute("to", "");
            string eo = nav.GetAttribute("evalOrder", "");
            fl.IdFrom = from;
            fl.IdTo = to;
            if (eo != null && eo.Length > 0)
                fl.EvalOrder = Int32.Parse(eo);
            string s = nav.GetAttribute("taskOutPort", "");
            if (s != null && s.Length > 0)
                fl.SourcePort = (TaskOutPortType) Enum.Parse(typeof(TaskOutPortType), s);
            s = nav.GetAttribute("cancelling", "");
            if (s != null && s.Length > 0 && ("1".Equals(s) || "true".Equals(s)))
                fl.IsCancelling = true;
            XPathNodeIterator it = nav.SelectChildren(XPathNodeType.Element);
            while (it.MoveNext())
            {
                if (it.Current.LocalName == "inputCondition")
                {
                    fl.InputCondition = it.Current.Value;
                }
                else throw new Exception("Invalid node in 'flow': " + it.Current.LocalName);
            }

            return fl;
        }


        
        
    }
}
